/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.search;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.spell.JaroWinklerDistance;
import org.apache.lucene.search.spell.LevensteinDistance;
import org.apache.lucene.search.spell.NGramDistance;
import org.apache.lucene.search.spell.StringDistance;
import org.apache.lucene.spatial.DistanceUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.schema.DateField;
import org.apache.solr.schema.LegacyDateField;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.schema.TrieDateField;
import org.apache.solr.search.function.BoostedQuery;
import org.apache.solr.search.function.ConstNumberSource;
import org.apache.solr.search.function.DivFloatFunction;
import org.apache.solr.search.function.DocValues;
import org.apache.solr.search.function.DoubleConstValueSource;
import org.apache.solr.search.function.DualFloatFunction;
import org.apache.solr.search.function.LinearFloatFunction;
import org.apache.solr.search.function.LiteralValueSource;
import org.apache.solr.search.function.MaxFloatFunction;
import org.apache.solr.search.function.MinFloatFunction;
import org.apache.solr.search.function.MultiValueSource;
import org.apache.solr.search.function.OrdFieldSource;
import org.apache.solr.search.function.ProductFloatFunction;
import org.apache.solr.search.function.QueryValueSource;
import org.apache.solr.search.function.RangeMapFloatFunction;
import org.apache.solr.search.function.ReciprocalFloatFunction;
import org.apache.solr.search.function.ReverseOrdFieldSource;
import org.apache.solr.search.function.ScaleFloatFunction;
import org.apache.solr.search.function.SimpleFloatFunction;
import org.apache.solr.search.function.SingleFunction;
import org.apache.solr.search.function.SumFloatFunction;
import org.apache.solr.search.function.TopValueSource;
import org.apache.solr.search.function.ValueSource;
import org.apache.solr.search.function.VectorValueSource;
import org.apache.solr.search.function.distance.GeohashFunction;
import org.apache.solr.search.function.distance.GeohashHaversineFunction;
import org.apache.solr.search.function.distance.HaversineConstFunction;
import org.apache.solr.search.function.distance.HaversineFunction;
import org.apache.solr.search.function.distance.SquaredEuclideanFunction;
import org.apache.solr.search.function.distance.StringDistanceFunction;
import org.apache.solr.search.function.distance.VectorDistanceFunction;
import org.apache.solr.util.plugin.NamedListInitializedPlugin;

import com.wistech.search.solr.extend.SalesFunction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A factory that parses user queries to generate ValueSource instances.
 * Intended usage is to create pluggable, named functions for use in function
 * queries.
 */
public abstract class ValueSourceParser implements NamedListInitializedPlugin {
	/**
	 * Initialize the plugin.
	 */
	public void init(NamedList args) {
	}

	/**
	 * Parse the user input into a ValueSource.
	 * 
	 * @param fp
	 * @throws ParseException
	 */
	public abstract ValueSource parse(FunctionQParser fp) throws ParseException;

	/* standard functions */
	public static Map<String, ValueSourceParser> standardValueSourceParsers = new HashMap<String, ValueSourceParser>();

	/**
	 * Adds a new parser for the name and returns any existing one that was
	 * overriden. This is not thread safe.
	 */
	public static ValueSourceParser addParser(String name, ValueSourceParser p) {
		return standardValueSourceParsers.put(name, p);
	}

	/**
	 * Adds a new parser for the name and returns any existing one that was
	 * overriden. This is not thread safe.
	 */
	public static ValueSourceParser addParser(NamedParser p) {
		return standardValueSourceParsers.put(p.name(), p);
	}

	private static void alias(String source, String dest) {
		standardValueSourceParsers.put(dest,
				standardValueSourceParsers.get(source));
	}

	static {
		addParser("ord", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				String field = fp.parseId();
				return new TopValueSource(new OrdFieldSource(field));
			}
		});
		addParser("literal", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				return new LiteralValueSource(fp.getString());
			}
		});
		addParser("rord", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				String field = fp.parseId();
				return new TopValueSource(new ReverseOrdFieldSource(field));
			}
		});
		addParser("top", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				// nested top is redundant, and ord and rord get automatically
				// wrapped
				if (source instanceof TopValueSource)
					return source;
				return new TopValueSource(source);
			}
		});
		addParser("linear", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				float slope = fp.parseFloat();
				float intercept = fp.parseFloat();
				return new LinearFloatFunction(source, slope, intercept);
			}
		});
		addParser("recip", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				float m = fp.parseFloat();
				float a = fp.parseFloat();
				float b = fp.parseFloat();
				return new ReciprocalFloatFunction(source, m, a, b);
			}
		});
		addParser("scale", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				float min = fp.parseFloat();
				float max = fp.parseFloat();
				return new TopValueSource(new ScaleFloatFunction(source, min,
						max));
			}
		});
		addParser("div", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource a = fp.parseValueSource();
				ValueSource b = fp.parseValueSource();
				return new DivFloatFunction(a, b);
			}
		});
		addParser("map", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				float min = fp.parseFloat();
				float max = fp.parseFloat();
				float target = fp.parseFloat();
				Float def = fp.hasMoreArguments() ? fp.parseFloat() : null;
				return new RangeMapFloatFunction(source, min, max, target, def);
			}
		});

		addParser("abs", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				return new SimpleFloatFunction(source) {
					@Override
					protected String name() {
						return "abs";
					}

					@Override
					protected float func(int doc, DocValues vals) {
						return Math.abs(vals.floatVal(doc));
					}
				};
			}
		});
		addParser("sum", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				List<ValueSource> sources = fp.parseValueSourceList();
				return new SumFloatFunction(sources
						.toArray(new ValueSource[sources.size()]));
			}
		});
		alias("sum", "add");

		addParser("product", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				List<ValueSource> sources = fp.parseValueSourceList();
				return new ProductFloatFunction(sources
						.toArray(new ValueSource[sources.size()]));
			}
		});
		alias("product", "mul");

		addParser("sub", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource a = fp.parseValueSource();
				ValueSource b = fp.parseValueSource();
				return new DualFloatFunction(a, b) {
					@Override
					protected String name() {
						return "sub";
					}

					@Override
					protected float func(int doc, DocValues aVals,
							DocValues bVals) {
						return aVals.floatVal(doc) - bVals.floatVal(doc);
					}
				};
			}
		});
		addParser("vector", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				return new VectorValueSource(fp.parseValueSourceList());
			}
		});
		addParser("query", new ValueSourceParser() {
			// boost(query($q),rating)
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				Query q = fp.parseNestedQuery();
				float defVal = 0.0f;
				if (fp.hasMoreArguments()) {
					defVal = fp.parseFloat();
				}
				return new QueryValueSource(q, defVal);
			}
		});
		addParser("boost", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				Query q = fp.parseNestedQuery();
				ValueSource vs = fp.parseValueSource();
				BoostedQuery bq = new BoostedQuery(q, vs);
				return new QueryValueSource(bq, 0.0f);
			}
		});

		addParser("geodist", HaversineConstFunction.parser);

		addParser("hsin", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {

				double radius = fp.parseDouble();
				// SOLR-2114, make the convert flag required, since the parser
				// doesn't support much in the way of lookahead or the ability
				// to convert a String into a ValueSource
				boolean convert = Boolean.parseBoolean(fp.parseArg());

				MultiValueSource pv1;
				MultiValueSource pv2;

				ValueSource one = fp.parseValueSource();
				ValueSource two = fp.parseValueSource();
				if (fp.hasMoreArguments()) {

					List<ValueSource> s1 = new ArrayList<ValueSource>();
					s1.add(one);
					s1.add(two);
					pv1 = new VectorValueSource(s1);
					ValueSource x2 = fp.parseValueSource();
					ValueSource y2 = fp.parseValueSource();
					List<ValueSource> s2 = new ArrayList<ValueSource>();
					s2.add(x2);
					s2.add(y2);
					pv2 = new VectorValueSource(s2);
				} else {
					// check to see if we have multiValue source
					if (one instanceof MultiValueSource
							&& two instanceof MultiValueSource) {
						pv1 = (MultiValueSource) one;
						pv2 = (MultiValueSource) two;
					} else {
						throw new SolrException(
								SolrException.ErrorCode.BAD_REQUEST,
								"Input must either be 2 MultiValueSources, or there must be 4 ValueSources");
					}
				}

				return new HaversineFunction(pv1, pv2, radius, convert);
			}
		});

		addParser("ghhsin", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				double radius = fp.parseDouble();

				ValueSource gh1 = fp.parseValueSource();
				ValueSource gh2 = fp.parseValueSource();

				return new GeohashHaversineFunction(gh1, gh2, radius);
			}
		});

		addParser("geohash", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {

				ValueSource lat = fp.parseValueSource();
				ValueSource lon = fp.parseValueSource();

				return new GeohashFunction(lat, lon);
			}
		});
		addParser("strdist", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {

				ValueSource str1 = fp.parseValueSource();
				ValueSource str2 = fp.parseValueSource();
				String distClass = fp.parseArg();

				StringDistance dist = null;
				if (distClass.equalsIgnoreCase("jw")) {
					dist = new JaroWinklerDistance();
				} else if (distClass.equalsIgnoreCase("edit")) {
					dist = new LevensteinDistance();
				} else if (distClass.equalsIgnoreCase("ngram")) {
					int ngram = 2;
					if (fp.hasMoreArguments()) {
						ngram = fp.parseInt();
					}
					dist = new NGramDistance(ngram);
				} else {
					dist = (StringDistance) fp.req.getCore()
							.getResourceLoader().newInstance(distClass);
				}
				return new StringDistanceFunction(str1, str2, dist);
			}
		});

		addParser(new DoubleParser("rad") {
			@Override
			public double func(int doc, DocValues vals) {
				return vals.doubleVal(doc) * DistanceUtils.DEGREES_TO_RADIANS;
			}
		});
		addParser(new DoubleParser("deg") {
			@Override
			public double func(int doc, DocValues vals) {
				return vals.doubleVal(doc) * DistanceUtils.RADIANS_TO_DEGREES;
			}
		});
		addParser(new DoubleParser("sqrt") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.sqrt(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("cbrt") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.cbrt(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("log") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.log10(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("ln") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.log(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("exp") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.exp(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("sin") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.sin(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("cos") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.cos(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("tan") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.tan(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("asin") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.asin(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("acos") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.acos(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("atan") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.atan(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("sinh") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.sinh(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("cosh") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.cosh(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("tanh") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.tanh(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("ceil") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.ceil(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("floor") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.floor(vals.doubleVal(doc));
			}
		});
		addParser(new DoubleParser("rint") {
			@Override
			public double func(int doc, DocValues vals) {
				return Math.rint(vals.doubleVal(doc));
			}
		});
		addParser(new Double2Parser("pow") {
			@Override
			public double func(int doc, DocValues a, DocValues b) {
				return Math.pow(a.doubleVal(doc), b.doubleVal(doc));
			}
		});
		addParser(new Double2Parser("hypot") {
			@Override
			public double func(int doc, DocValues a, DocValues b) {
				return Math.hypot(a.doubleVal(doc), b.doubleVal(doc));
			}
		});
		addParser(new Double2Parser("atan2") {
			@Override
			public double func(int doc, DocValues a, DocValues b) {
				return Math.atan2(a.doubleVal(doc), b.doubleVal(doc));
			}
		});
		addParser("max", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				List<ValueSource> sources = fp.parseValueSourceList();
				return new MaxFloatFunction(sources
						.toArray(new ValueSource[sources.size()]));
			}
		});
		addParser("min", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				List<ValueSource> sources = fp.parseValueSourceList();
				return new MinFloatFunction(sources
						.toArray(new ValueSource[sources.size()]));
			}
		});
		addParser("sqedist", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				List<ValueSource> sources = fp.parseValueSourceList();
				MVResult mvr = getMultiValueSources(sources);

				return new SquaredEuclideanFunction(mvr.mv1, mvr.mv2);
			}
		});

		addParser("dist", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				float power = fp.parseFloat();
				List<ValueSource> sources = fp.parseValueSourceList();
				MVResult mvr = getMultiValueSources(sources);
				return new VectorDistanceFunction(power, mvr.mv1, mvr.mv2);
			}
		});
		addParser("ms", new DateValueSourceParser());

		addParser("pi", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				return new DoubleConstValueSource(Math.PI);
			}
		});
		addParser("e", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				return new DoubleConstValueSource(Math.E);
			}
		});

		addParser("sales", new ValueSourceParser() {
			@Override
			public ValueSource parse(FunctionQParser fp) throws ParseException {
				ValueSource source = fp.parseValueSource();
				return new SalesFunction(source);
			}
		});
	}

	private static void splitSources(int dim, List<ValueSource> sources,
			List<ValueSource> dest1, List<ValueSource> dest2) {
		// Get dim value sources for the first vector
		for (int i = 0; i < dim; i++) {
			dest1.add(sources.get(i));
		}
		// Get dim value sources for the second vector
		for (int i = dim; i < sources.size(); i++) {
			dest2.add(sources.get(i));
		}
	}

	private static MVResult getMultiValueSources(List<ValueSource> sources) {
		MVResult mvr = new MVResult();
		if (sources.size() % 2 != 0) {
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"Illegal number of sources.  There must be an even number of sources");
		}
		if (sources.size() == 2) {

			// check to see if these are MultiValueSource
			boolean s1MV = sources.get(0) instanceof MultiValueSource;
			boolean s2MV = sources.get(1) instanceof MultiValueSource;
			if (s1MV && s2MV) {
				mvr.mv1 = (MultiValueSource) sources.get(0);
				mvr.mv2 = (MultiValueSource) sources.get(1);
			} else if (s1MV || s2MV) {
				// if one is a MultiValueSource, than the other one needs to be
				// too.
				throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
						"Illegal number of sources.  There must be an even number of sources");
			} else {
				mvr.mv1 = new VectorValueSource(
						Collections.singletonList(sources.get(0)));
				mvr.mv2 = new VectorValueSource(
						Collections.singletonList(sources.get(1)));
			}
		} else {
			int dim = sources.size() / 2;
			List<ValueSource> sources1 = new ArrayList<ValueSource>(dim);
			List<ValueSource> sources2 = new ArrayList<ValueSource>(dim);
			// Get dim value sources for the first vector
			splitSources(dim, sources, sources1, sources2);
			mvr.mv1 = new VectorValueSource(sources1);
			mvr.mv2 = new VectorValueSource(sources2);
		}

		return mvr;
	}

	private static class MVResult {
		MultiValueSource mv1;
		MultiValueSource mv2;
	}
}

class DateValueSourceParser extends ValueSourceParser {
	DateField df = new TrieDateField();

	@Override
	public void init(NamedList args) {
	}

	public Date getDate(FunctionQParser fp, String arg) {
		if (arg == null)
			return null;
		if (arg.startsWith("NOW")
				|| (arg.length() > 0 && Character.isDigit(arg.charAt(0)))) {
			return df.parseMathLenient(null, arg, fp.req);
		}
		return null;
	}

	public ValueSource getValueSource(FunctionQParser fp, String arg) {
		if (arg == null)
			return null;
		SchemaField f = fp.req.getSchema().getField(arg);
		if (f.getType().getClass() == DateField.class
				|| f.getType().getClass() == LegacyDateField.class) {
			throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
					"Can't use ms() function on non-numeric legacy date field "
							+ arg);
		}
		return f.getType().getValueSource(f, fp);
	}

	@Override
	public ValueSource parse(FunctionQParser fp) throws ParseException {
		String first = fp.parseArg();
		String second = fp.parseArg();
		if (first == null)
			first = "NOW";

		Date d1 = getDate(fp, first);
		ValueSource v1 = d1 == null ? getValueSource(fp, first) : null;

		Date d2 = getDate(fp, second);
		ValueSource v2 = d2 == null ? getValueSource(fp, second) : null;

		// d constant
		// v field
		// dd constant
		// dv subtract field from constant
		// vd subtract constant from field
		// vv subtract fields

		final long ms1 = (d1 == null) ? 0 : d1.getTime();
		final long ms2 = (d2 == null) ? 0 : d2.getTime();

		// "d,dd" handle both constant cases

		if (d1 != null && v2 == null) {
			return new LongConstValueSource(ms1 - ms2);
		}

		// "v" just the date field
		if (v1 != null && v2 == null && d2 == null) {
			return v1;
		}

		// "dv"
		if (d1 != null && v2 != null)
			return new DualFloatFunction(new LongConstValueSource(ms1), v2) {
				@Override
				protected String name() {
					return "ms";
				}

				@Override
				protected float func(int doc, DocValues aVals, DocValues bVals) {
					return ms1 - bVals.longVal(doc);
				}
			};

		// "vd"
		if (v1 != null && d2 != null)
			return new DualFloatFunction(v1, new LongConstValueSource(ms2)) {
				@Override
				protected String name() {
					return "ms";
				}

				@Override
				protected float func(int doc, DocValues aVals, DocValues bVals) {
					return aVals.longVal(doc) - ms2;
				}
			};

		// "vv"
		if (v1 != null && v2 != null)
			return new DualFloatFunction(v1, v2) {
				@Override
				protected String name() {
					return "ms";
				}

				@Override
				protected float func(int doc, DocValues aVals, DocValues bVals) {
					return aVals.longVal(doc) - bVals.longVal(doc);
				}
			};

		return null; // shouldn't happen
	}

}

// Private for now - we need to revisit how to handle typing in function queries

class LongConstValueSource extends ConstNumberSource {
	final long constant;
	final double dv;
	final float fv;

	public LongConstValueSource(long constant) {
		this.constant = constant;
		this.dv = constant;
		this.fv = constant;
	}

	@Override
	public String description() {
		return "const(" + constant + ")";
	}

	@Override
	public DocValues getValues(Map context, IndexReader reader)
			throws IOException {
		return new DocValues() {
			@Override
			public float floatVal(int doc) {
				return fv;
			}

			@Override
			public int intVal(int doc) {
				return (int) constant;
			}

			@Override
			public long longVal(int doc) {
				return constant;
			}

			@Override
			public double doubleVal(int doc) {
				return dv;
			}

			@Override
			public String strVal(int doc) {
				return Long.toString(constant);
			}

			@Override
			public String toString(int doc) {
				return description();
			}
		};
	}

	@Override
	public int hashCode() {
		return (int) constant + (int) (constant >>> 32);
	}

	@Override
	public boolean equals(Object o) {
		if (LongConstValueSource.class != o.getClass())
			return false;
		LongConstValueSource other = (LongConstValueSource) o;
		return this.constant == other.constant;
	}

	@Override
	public int getInt() {
		return (int) constant;
	}

	@Override
	public long getLong() {
		return constant;
	}

	@Override
	public float getFloat() {
		return fv;
	}

	@Override
	public double getDouble() {
		return dv;
	}

	@Override
	public Number getNumber() {
		return constant;
	}
}

abstract class NamedParser extends ValueSourceParser {
	private final String name;

	public NamedParser(String name) {
		this.name = name;
	}

	public String name() {
		return name;
	}
}

abstract class DoubleParser extends NamedParser {
	public DoubleParser(String name) {
		super(name);
	}

	public abstract double func(int doc, DocValues vals);

	@Override
	public ValueSource parse(FunctionQParser fp) throws ParseException {
		return new Function(fp.parseValueSource());
	}

	class Function extends SingleFunction {
		public Function(ValueSource source) {
			super(source);
		}

		@Override
		public String name() {
			return DoubleParser.this.name();
		}

		@Override
		public DocValues getValues(Map context, IndexReader reader)
				throws IOException {
			final DocValues vals = source.getValues(context, reader);
			return new DocValues() {
				@Override
				public float floatVal(int doc) {
					return (float) doubleVal(doc);
				}

				@Override
				public int intVal(int doc) {
					return (int) doubleVal(doc);
				}

				@Override
				public long longVal(int doc) {
					return (long) doubleVal(doc);
				}

				@Override
				public double doubleVal(int doc) {
					return func(doc, vals);
				}

				@Override
				public String strVal(int doc) {
					return Double.toString(doubleVal(doc));
				}

				@Override
				public String toString(int doc) {
					return name() + '(' + vals.toString(doc) + ')';
				}
			};
		}
	}
}

abstract class Double2Parser extends NamedParser {
	public Double2Parser(String name) {
		super(name);
	}

	public abstract double func(int doc, DocValues a, DocValues b);

	@Override
	public ValueSource parse(FunctionQParser fp) throws ParseException {
		return new Function(fp.parseValueSource(), fp.parseValueSource());
	}

	class Function extends ValueSource {
		private final ValueSource a;
		private final ValueSource b;

		/**
		 * @param a
		 *            the base.
		 * @param b
		 *            the exponent.
		 */
		public Function(ValueSource a, ValueSource b) {
			this.a = a;
			this.b = b;
		}

		@Override
		public String description() {
			return name() + "(" + a.description() + "," + b.description() + ")";
		}

		@Override
		public DocValues getValues(Map context, IndexReader reader)
				throws IOException {
			final DocValues aVals = a.getValues(context, reader);
			final DocValues bVals = b.getValues(context, reader);
			return new DocValues() {
				@Override
				public float floatVal(int doc) {
					return (float) doubleVal(doc);
				}

				@Override
				public int intVal(int doc) {
					return (int) doubleVal(doc);
				}

				@Override
				public long longVal(int doc) {
					return (long) doubleVal(doc);
				}

				@Override
				public double doubleVal(int doc) {
					return func(doc, aVals, bVals);
				}

				@Override
				public String strVal(int doc) {
					return Double.toString(doubleVal(doc));
				}

				@Override
				public String toString(int doc) {
					return name() + '(' + aVals.toString(doc) + ','
							+ bVals.toString(doc) + ')';
				}
			};
		}

		@Override
		public void createWeight(Map context, Searcher searcher)
				throws IOException {
			a.createWeight(context, searcher);
			b.createWeight(context, searcher);
		}

		@Override
		public int hashCode() {
			int h = a.hashCode();
			h ^= (h << 13) | (h >>> 20);
			h += b.hashCode();
			h ^= (h << 23) | (h >>> 10);
			h += name().hashCode();
			return h;
		}

		@Override
		public boolean equals(Object o) {
			if (this.getClass() != o.getClass())
				return false;
			Function other = (Function) o;
			return this.a.equals(other.a) && this.b.equals(other.b);
		}
	}

}
